/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef SIMULATORENGINE_H
#define SIMULATORENGINE_H

#include "SimInterface/ISimulator.h"
#include "SimInterface/IConfigurable.h"
#include "SimInterface/IDiscreteObject.h"
#include "ExternalPlant.h"
#include "ControllerBase.h"
#include "DiscreteObject.h"
#include "WaveformBase.h"
#include "WaveformContainer.h"

#include "SimFile/ConfigReader.h"

#include "Jano/NonlinearityBase.h"

#include <memory>
#include <list>

#include <boost/smart_ptr.hpp>

#include <iosfwd>

namespace RafMat
{
namespace SimEngine
{

// Deklaracje zapowiadające
class SISOModel;

/**
    Implementacja silnika symulatora.

    Wg założeń jest to klasa do symulacji pętli regulacji (punkt 4. ramowego opisu).
    Implementacja wykorzystywana jest przez GUI nie wprost - poprzez ogólny interfejs
    ISimulator jako punkt styku.

    Jądrem silnika jest metoda doStep, która realizuje pracę krokową symulatora
    propagując odpowiednie sygnały. Niemniej ważne są metody umożliwiające złożenie
    układu do kupy. Chodzi głównie o metody do edycji wartości wejścia, regulatora
    oraz łańcucha obiektów.
*/
class SimulatorEngine : public SimInterface::ISimulator
{
public:

    /**
        Tworzy instancję symulatora.

        Zadaniem konstruktora jest złożenie najprostszego poprawnie działającego modelu.
        Początkowo układ symulacji inicjalizowany jest z pseudoregulatorem, więc
        pętla regulacji jest otwarta. Ponieważ domyślnie lista obiektów jest pusta,
        wykorzystywany jest obiekt domyślny typu bypass.
    */
    SimulatorEngine();

    /**
        Realizuje jeden krok symulacji.

        Funkcja propaguje sygnały przez cały układ symulacji. Dzieje się to w następującej
        kolejności:
        -# Wykonanie kroku przez regulator (realizacja pętli sprzężenia zwrotnego).
        -# Propagacja wyjścia regulatora przez wszystkie obiekty w łańcuchu obiektów.
        -# Ustawienie wyjścia modelu i licznika próbek.
    */
    void doStep();

    /**
        Resetuje całą symulację.

        Funkcja przywraca symulację do stanu w chwili 0.
        Funkcja nie zmienia parametrów modeli, z którymi pracuje.
    */
    void reset();

    /**
        Zwraca numer aktualnej próbki.

        \return Nr próbki jako liczba nieujemna (całkowita).
    */
    unsigned int currentSampleNum() const { return m_sample; }

    /**
        Zwraca aktualną wartość wejścia układu symulacji.
        Jest to po prostu aktualne wejście regulatora.
    */
    double getInputValue() const { return m_currentInput;}

    /**
        Zwraca aktualną wartość wyjścia układu regulacji.
        Jest to wyjście ostatniego modelu w łańcuchu obiektów regulacji.
    */
    double getOutputValue() const { return m_currentOutputTrick;}

    /**
        Zwraca aktualne wyjście regulatora.
    */
    double getControllerOutput() const { return m_currentControlTrick;}

    /**
        Tworzy nowy obiekt i dodaje go do łańcucha obiektów.

        \param [in] aDegree Maksymalny stopień wielomianu licznika transmitancji.
        \param [in] bDegree Maksymalny stopień wielomianu mianownika transmitancji.
        \param [in] delay Maksymalne opóźnienie.
        \return Interfejs do obiektu dyskretnego.
    */
    SimInterface::IDiscreteObjectSPtr createObject(int aDegree, int bDegree, int cDegree, int agDegree, int d, int delay = 0);

    /**
        Usuwa obiekt z łańcucha obiektów.

        \param [in] obj Zręczny wskaźnik do usuwanego obiektu.
    */
    void removeObject(SimInterface::IDiscreteObjectSPtr obj);

    /**
        Zwraca aktualny regulator pętli regulacji.
        \return Interfejs konfiguracyjny regulatora.
    */
    SimInterface::IConfigurableSPtr getController();

    /**
        Tworzy nowy regulator.

        Nowy regulator zastępuje stary! Istnieje możliwość skopiowania
        dotychczasowych wartości zadanych.

        \param [in] ctrlType Typ regulatora.
        \param [in] copyInputs Flaga kopiowania wejść. True jeśli wejścia
        mają zostać skopiowane, false - jeśli regulator ma być "czysty" z wyzerowaną
        wartością zadaną.
    */
    SimInterface::IConfigurableSPtr createController(ControllerType ctrlType,
                                                     bool copyInputs);

    /**
        Dodaje sygnał do wartości zadawanych regulatora.

        \param [in] inputType Typ sygnału.
        \return Interfejs konfiguracyjny dla sygnału.
    */
    SimInterface::IConfigurableSPtr addInput(InputType inputType);

    SimInterface::IConfigurableSPtr setNonlinearity(NonlinearityType nlType);

    /**
        Usuwa sygnał ze zbioru wartości zadawanych regulatora.

        \param [in] iConf Wskaźnik dla usuwanego wejścia.
    */
    void removeInput(SimInterface::IConfigurableSPtr iConf);

    ControllerMap getControllerMap() const;

    InputMap getInputMap() const;

    NLMap getNonlinearityMap() const { return m_strToNL; }

    SimInterface::IConfigurableSPtr getExternalPlant() const;
    SimInterface::IConfigurableSPtr enableExternalPlant(bool enable);


    void setCaptureFilePath(const std::string& path);
    void enableDataCapture(bool enable);

    /**
        Zapisuje konfigurację do pliku.

        \param [in] filePath Plik docelowy.
    */
    void saveConfiguration(std::string filePath);

    void loadConfiguration(std::string filePath);

    /**
        Zwraca wszystkie obiekty dynamiczne.
    */
    std::vector<SimInterface::IDiscreteObjectSPtr> getObjects() const;

    /**
        Zwraca wszystkie wejścia.
    */
    std::vector<SimInterface::IConfigurableSPtr> getInputs() const;

    SimInterface::IConfigurableSPtr getNonlinearityObj() const;

private:

    /** Czyści zawartość symulatora.
      Usuwa wszystko - wejścia, modele, regulatory etc. */
    void clearAll();

    // Pomocnicze metody dla loadConfiguration.
    void loadConfModel(SimFile::BlockInfoSPtr modelBlock);
    void loadConfController(SimFile::BlockInfoSPtr ctrlBlock);
    void loadConfInput(SimFile::BlockInfoSPtr inputBlock);
    void loadConfNonlinearity(SimFile::BlockInfoSPtr nlBlock);

    /** "Przepompowuje" parametry z pliku do obiektu zgodnego z IConfigurable. */
    void loadConfPumpParams(SimFile::BlockInfoSPtr blk, SimInterface::IConfigurableSPtr target);

    double applyNonlinearity(double input);

    void captureData();

    std::list<DiscreteObjectSPtr> m_objectChain;     ///< Łańcuch obiektów.

    unsigned int m_sample;                      ///< Aktualny nr próbki.

    double m_currentInput;                      ///< Aktualne wejście układu.
    double m_currentOutput;                     ///< Aktualne wyjście układu.
    double m_currentOutputTrick;                ///< Poprzednia próbka - dla wykresów.
    double m_currentControlTrick;

    ControllerBaseSPtr m_controller;            ///< Obiekt regulatora.

    bool m_nonlinearityEnabled;                 ///< Aktywność części nieliniowej.
    NonlinearityBaseSPtr m_outputNL;            ///< Nieliniowość wyjściowa

    // pomocnicze mapy:
    ControllerMap m_strToCtrl;
    InputMap m_strToInput;
    NLMap m_strToNL;

    // Do zewnętrznego układu dynamicznego (z biblioteki dynamicznej)
    bool m_externalModeEnabled;
    ExternalPlantSPtr m_externalPlant;

    bool m_captureEnabled;
    std::string m_capturePath;

    boost::shared_ptr<std::ofstream> m_captureStream;
};

}   // SimInterface namespace
}   // RafMat namespace



#endif // SIMULATORENGINE_H
